﻿using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using Spring.Context.Support;
using Spring.Core.IO;
using Spring.Validation;

namespace Mistral.Xml
{
	/// <summary>
	/// Класс для работы с Xml-схемами.
	/// </summary>
	public class XmlSchemas
	{
		#region Fields

		private readonly XmlSchemaSet _schemaSet = new XmlSchemaSet();

		#endregion

		#region .ctor

		/// <summary>
		/// Конструктор
		/// </summary>
		/// <param name="schemaLocations">Размещение схем в формате адреса ресурса в Spring.NET</param>
		public XmlSchemas( params string[] schemaLocations )
		{
			// Грузим схемы из ресурсов
			foreach ( string schemaLocation in schemaLocations )
			{
				ConfigurableResourceLoader resourceLoader = new ConfigurableResourceLoader();
				IResource resource;
				try
				{
					resource = resourceLoader.GetResource( schemaLocation );
					XmlSchema schema = XmlSchema.Read( resource.InputStream, null );
					_schemaSet.Add( schema );
				}
				catch ( Exception e )
				{
					throw new Exception( "Схема не может быть загружена из "+schemaLocation, e );
				}
			}
			_schemaSet.Compile();
		}

		#endregion

		#region Properties

		/// <summary>
		/// Схемы
		/// </summary>
		public XmlSchemaSet SchemaSet
		{
			get { return _schemaSet; }
		}

		#endregion

		#region Methods

		/// <summary>
		/// Создание XmlReader с валидацией по схеме
		/// </summary>
		/// <param name="stream">Поток данных</param>
		/// <returns>XmlReader</returns>
		public XmlReader CreateValidatingReader( TextReader stream )
		{
			return CreateValidatingReader( stream, DefaultValidationEventHandler );
		}

		/// <summary>
		/// Создание XmlReader с валидацией по схеме
		/// </summary>
		/// <param name="stream">Поток данных</param>
		/// <param name="validationEventHandler">Делегат для обработки ошибок</param>
		/// <returns>XmlReader</returns>
		public XmlReader CreateValidatingReader( TextReader stream, ValidationEventHandler validationEventHandler )
		{
			// XmlReader с валидацией по схеме
			XmlReaderSettings settings = new XmlReaderSettings();
			settings.CloseInput = false;
			settings.Schemas = SchemaSet;
			settings.ValidationType = ValidationType.Schema;
			settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;

			if( validationEventHandler != null )
				settings.ValidationEventHandler += validationEventHandler;

			return XmlReader.Create( stream, settings );
		}

		/// <summary>
		/// Чтение документа из потока с валидацией по схеме
		/// </summary>
		/// <param name="stream">Поток данных</param>
		/// <returns>XmlDocument</returns>
		public XmlDocument ReadValidatedDocument( TextReader stream )
		{
			return ReadValidatedDocument( stream, DefaultValidationEventHandler );
		}

		/// <summary>
		/// Чтение документа из потока с валидацией по схеме
		/// </summary>
		/// <param name="stream">Поток данных</param>
		/// <param name="validationEventHandler">Делегат для обработки ошибок</param>
		/// <returns>XmlDocument</returns>
		public XmlDocument ReadValidatedDocument( TextReader stream, ValidationEventHandler validationEventHandler )
		{
			// Чтение документа
			XmlReader xmlReader = CreateValidatingReader( stream, validationEventHandler );
			XmlDocument document = new XmlDocument();
			document.Load( xmlReader );
			return document;
		}

		#endregion

		#region Implementation

		/// <summary>
		/// Реализация обработки ошибок по умолчанию.
		/// Выкидывает исключение типа <see cref="ValidationException"/> в случае ошибки валидации.
		/// </summary>
		private static void DefaultValidationEventHandler( object sender, ValidationEventArgs e )
		{
			if ( e.Severity == XmlSeverityType.Error )
			{
				string error = String.Format(
					"Не соответствует схеме:\nMessage:{0} Line:{1}, Position:{2}, SourceUri:{3}",
					e.Message, e.Exception.LineNumber, e.Exception.LinePosition, e.Exception.SourceUri );
				throw new ValidationException( error, e.Exception );
			}
			
		}

		#endregion
	}
}